/*
* Copyright 2010 Pavel Kozhin 
*
* Licensed under the Apache License, Version 2.0 (the "License"); 
* you may not use this file except in compliance with the License. 
* You may obtain a copy of the License at 
* 
* http://www.apache.org/licenses/LICENSE-2.0
* 
* Unless required by applicable law or agreed to in writing, software 
* distributed  under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
* See the License for the specific language governing permissions and 
* limitations under the License.
*
* Author: Pavel Kozhin.
*/

package org.bookmarksframework.core
{
	public class Path implements IFacileHierarchical
	{
		private var _strict:Boolean = false;

		public function get strict():Boolean
		{
			return _strict;
		}

		public function set strict(value:Boolean):void
		{
			_strict = value;
		}
		
		private var _separator:String;

		public function get separator():String
		{
			return _separator;
		}

		public function set separator(value:String):void
		{
			_separator = value;
		}
		
		private var _solid:Boolean = false;

		public function get solid():Boolean
		{
			return _solid;
		}

		public function set solid(value:Boolean):void
		{
			_solid = value;
		}
		
		private var _context:* = null;
		
		[Bindable]
		public function get context():*
		{
			return (strict) ? qualify(_context) : _context;
		}

		public function set context(value:*):void
		{
			_context = value;
		}
		
		private var _value:*;
		
		[Bindable]
		public function get value():*
		{
			return (strict) ? qualify(_value) : _value;
		}

		public function set value(value:*):void
		{
			if (value is String)
			{
				_value = value;
				_map = getMap(value);
			}
			else
			{
				_value = null;
				_map = null;
			}
		}
		
		private var _map:XML;

		[Bindable]
		public function get map():XML
		{
			return _map;
		}
		
		public function set map(value:XML):void
		{
			if (value)
			{
				_map = value;
				_value = getValue(value);
			}
			else
			{
				_map = null;
				_value = null;
			}
		}
		
		public function getFragment(fragment:IFragment):Path
		{
			var result:Path = null;
			var map:XML = this.map;
			var child:XML = map..*.(@uid == fragment.uid)[0];
			
			if (child.childIndex() != -1)
			{
				map = <map>{child}</map>;
				result = clone();
				result.map = map;
				result.context = result.value;
			}
			else if (fragment.part is String && 
				fragment.part.length)
			{
				map = <map></map>;
				map.appendChild(<{fragment.part}/>);
				result = clone();
				result.map = map;
				result.context = result.value;
			}
			
			return result;
		}
		
		public function updateFragment(item:FacileHierarchy, 
									   fragment:IFragment):void
		{
			if (item.facileKey is XML)
			{
				item.facileKey.@uid = fragment.uid;
			}
		}
		
		public function getKey(value:*):*
		{
			return (value is String) ? 
				map.descendants(value)[0] : null;
		}
		
		public function getItem(facileKey:*):FacileHierarchy
		{
			var result:FacileHierarchy = null;
			
			if (facileKey is XML)
			{
				result = new FacileHierarchy(this, facileKey, 
					facileKey.localName());
			}
			
			return result; 
		}
		
		public function getParent(facileKey:*):FacileHierarchy
		{
			var result:FacileHierarchy = null;
			var child:XML = facileKey as XML;
			var parent:XML = (child) ? child.parent() : null;
			
			if (parent && parent.childIndex() >= 0)
			{
				result = new FacileHierarchy(this, parent, 
					parent.localName());
			}
			
			return result;
		}
		
		public function getChildren(facileKey:* = null):Vector.<FacileHierarchy>
		{
			var scope:Path = this;
			var f:Function = function(children:XMLList):Vector.<FacileHierarchy>
			{
				var result:Vector.<FacileHierarchy> = new Vector.<FacileHierarchy>();
				var childrenLen:int = children.length();
				var hierarchy:FacileHierarchy;
				var child:XML;
				
				for (var i:int = 0; i < childrenLen; ++i)
				{
					child = children[i];
					hierarchy = getItem(child);
					result.push(hierarchy);
				}
				
				return result;
			};
			
			return f((facileKey is XML) ? facileKey.children() : map.children());
		}
		
		public function clone():Path
		{
			var result:Path = new Path();
			result.separator = separator;
			result.solid = solid;
			
			return result;
		}
		
		protected function getValue(path:XML):*
		{
			return getValueRecursively(path.children());
		}
		
		protected function getValueRecursively(children:XMLList):String
		{
			var result:String = "";
			var child:XML;
			var tmp:String;
			
			for (var i:int = 0; i < children.length(); ++i)
			{
				child = children[i];
				
				if (children.length() > 1)
				{
					tmp = arguments.callee.call(null, child.children());
					result += "(" + child.localName() + (tmp ? separator + tmp : "") + ")";
				}
				else
				{
					tmp = arguments.callee.call(null, child.children());
					result += child.localName() + (tmp ? separator + tmp : "");
				}
			}
			
			return result;
		}
		
		protected function getMap(path:*):XML
		{
			var result:XML = null;
			
			if (path is String)
			{
				var str:String = path as String;
				var a:int = str.search(/[^A-Za-z0-9_\/\(\)]/g);
				if (a >= 0)
					throw new Error("Path is not well-formed.");
				
				a = str.search(/\)[^\(\)]/g);
				if (a >= 0)
					throw new Error("Path is not well-formed.");
				
				var b:Array = str.match(/\(/g);
				var c:Array = str.match(/\)/g);
				
				if (b.length != c.length)
					throw new Error("Path is not well-formed.");
				
				result = parseRecursively(str);
			}
			
			return result;
		}
		
		protected function parseRecursively(str:String, xml:XML = null):XML
		{
			if (!xml)
				xml = <map/>;
			
			if (!str.length)
				return xml;
			
			var index:int;
			var node:XML;
			var descendants:XMLList;
			var openIndex:int = -1;
			var closeIndex:int = -1;
			var openCounter:int = 0;
			var closeCounter:int = 0;
			var strLen:int = str.length;
			var char:String;
			
			if ((index = str.indexOf("(")) > 0)
			{
				node = convertPath(str.slice(0, index));
				if (node)
				{
					str = str.substr(index);
					xml.appendChild(node);
				}
			}
			else if ((str.indexOf("(") < 0) && 
				(str.indexOf(")") < 0))
			{
				node = convertPath(str);
				if (node)
				{
					xml.appendChild(node);
				}
			}
			
			if (node)
			{
				if (node.children().length())
				{
					descendants = node.descendants(); 
					node = descendants[descendants.length()-1];
				}
			}
			else
			{
				node = xml;
			}
			
			for (var i:int = 0; i < strLen; ++i)
			{
				char = str.charAt(i);
				if (char == "(")
				{
					openCounter++;
					if(openIndex < 0)
						openIndex = i;
				}
				
				if (char == ")")
				{
					closeCounter++;
					closeIndex = i;
				}
				
				if ((openIndex != -1) && 
					(openCounter == closeCounter))
				{
					arguments.callee.call(null, 
						str.substring(openIndex + 1, closeIndex), node);
					openIndex = -1;
					closeIndex = -1;
					openCounter = 0;
					closeCounter = 0;
				}
			}
			
			return xml;
		}
		
		protected function convertPath(path:String):XML
		{
			var arr:Array = path.split(separator);
			var arrLen:int = arr.length;
			var result:XML;
			var node:XML;
			var child:XML;
			
			for (var i:int = 0; i < arrLen; ++i)
			{
				if (!arr[i])
					continue;
				
				node = <{arr[i]}/>;
				
				if (child)
					child.appendChild(node);
				
				child = node;
				
				if (!result)
					result = child;
			}
			
			return result;
		}
		
		protected function qualify(str:String):String
		{
			if (str && str.indexOf(separator) != 0)
				str = separator + str;
			
			return str;
		}
		
	}
}